////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//         This file is distributed under the BSD 2-Clause open source license. See Licenses/License.txt for details.         //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNScripting/inc/WNScriptingErrors.h"
#include "WNScripting/inc/WNBuiltinTypeInitialization.h"
#include "WNScripting/inc/WNTypeManager.h"
#include "WNScripting/inc/WNExpression.h"
#include "WNMemory/inc/WNAllocation.h"

#ifdef _WN_MSVC
    #pragma warning(push)
    #pragma warning(disable: 4100)
    #pragma warning(disable: 4127)
    #pragma warning(disable: 4152)
    #pragma warning(disable: 4244)
    #pragma warning(disable: 4245)
    #pragma warning(disable: 4267)
    #pragma warning(disable: 4355)
    #pragma warning(disable: 4512)
#endif

#include "llvm/IR/IRBuilder.h"

#ifdef _WN_MSVC
    #pragma warning(pop)
#endif

#include <stdlib.h>

namespace WNScripting {

    template<llvm::Value* (llvm::IRBuilder<>::*T)(llvm::Value*, llvm::Value*, const llvm::Twine&, bool, bool)>
    struct GenerateIntArithmetic : public GenerateArithmeticOperation {
        GenerateIntArithmetic(WNScriptType _destType) :
            mDestFlt(_destType) {
        }
        virtual ~GenerateIntArithmetic() {}
        virtual eWNTypeError Execute(llvm::IRBuilderBase* _builder, llvm::Value* _expr1, llvm::Value* _expr2, WNScriptType& _destType, llvm::Value*& _outReturnVal) const {
            llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_builder);       
            _outReturnVal = ((*builder).*T)(_expr1, _expr2, "", false, false);
            _destType = mDestFlt;
            return(eWNOK);
        }
    private:
        WNScriptType mDestFlt;
    };

    template<llvm::Value* (llvm::IRBuilder<>::*T)(llvm::Value*, llvm::Value*, const llvm::Twine&)>
    struct GenerateIntCompare : public GenerateArithmeticOperation {
        GenerateIntCompare(WNScriptType _destType) :
            mDestFlt(_destType) {
        }
        virtual ~GenerateIntCompare() {}
        virtual eWNTypeError Execute(llvm::IRBuilderBase* _builder, llvm::Value* _expr1, llvm::Value* _expr2, WNScriptType& _destType, llvm::Value*& _outReturnVal) const {
            llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_builder);
            _outReturnVal = ((*builder).*T)(_expr1, _expr2, "");
            _outReturnVal = builder->CreateIntCast(_outReturnVal, mDestFlt->mLLVMType, false, "");
            _destType = mDestFlt;
            return(eWNOK);
        }
    private:
        WNScriptType mDestFlt;
    };

    template<llvm::Value* (llvm::IRBuilder<>::*T)(llvm::Value*, llvm::Value*, const llvm::Twine&, bool)>
    struct GenerateIntDivArithmetic : public GenerateArithmeticOperation {
        GenerateIntDivArithmetic(WNScriptType _destType) :
            mDestFlt(_destType) {
        }
        virtual ~GenerateIntDivArithmetic() {}
        virtual eWNTypeError Execute(llvm::IRBuilderBase* _builder, llvm::Value* _expr1, llvm::Value* _expr2, WNScriptType& _destType, llvm::Value*& _outReturnVal) const {
            llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_builder);       
            _outReturnVal = ((*builder).*T)(_expr1, _expr2, "", false);
            _destType = mDestFlt;
            return(eWNOK);
        }
    private:
        WNScriptType mDestFlt;
    };

    template<llvm::Value* (llvm::IRBuilder<>::*T)(llvm::Value*, llvm::Value*, const llvm::Twine&)>
    struct GenerateIntModArithmetic : public GenerateArithmeticOperation {
        GenerateIntModArithmetic (WNScriptType _destType) :
            mDestFlt(_destType) {
        }
        virtual ~GenerateIntModArithmetic() {}
        virtual eWNTypeError Execute(llvm::IRBuilderBase* _builder, llvm::Value* _expr1, llvm::Value* _expr2, WNScriptType& _destType, llvm::Value*& _outReturnVal) const {
            llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_builder);       
            _outReturnVal = ((*builder).*T)(_expr1, _expr2, "");
            _destType = mDestFlt;
            return(eWNOK);
        }
    private:
        WNScriptType mDestFlt;
    };

    template<llvm::Value* (llvm::IRBuilder<>::*T)(llvm::Value*, llvm::Value*, const llvm::Twine&, bool, bool)>
    eWNTypeError GeneratePreValue(llvm::IRBuilderBase* _builder, const WNExpression& _expr1, llvm::Value*& _value) {
        llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_builder);
        llvm::Type* type = _expr1.GetType()->mLLVMType;
        llvm::Value* val = llvm::ConstantInt::get(type, 1);
        _value = ((*builder).*T)(_expr1.GetValue(), val, "", false, false);
        if(WN_NULL != _expr1.GetValueLocation()) {
            builder->CreateStore(_value, _expr1.GetValueLocation(), false);
        }
        return(eWNOK);
    }

    template<llvm::Value* (llvm::IRBuilder<>::*T)(llvm::Value*, llvm::Value*, const llvm::Twine&, bool, bool)>
    eWNTypeError GeneratePostValue(llvm::IRBuilderBase* _builder, const WNExpression& _expr1, llvm::Value*& _value) {
        llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_builder);       
        if(WN_NULL == _expr1.GetValueLocation()) {
            return(eWNInvalidParameters);
        }
        _value = _expr1.GetValue();
        llvm::Type* type = _expr1.GetType()->mLLVMType;
        llvm::Value* val = llvm::ConstantInt::get(type, 1);
        llvm::Value* postVal = ((*builder).*T)(_expr1.GetValue(), val, "", false, false);
        builder->CreateStore(postVal, _expr1.GetValueLocation(), false);
        return(eWNOK);
    }

    eWNTypeError GenerateBoolNegation(llvm::IRBuilderBase* _builder, const WNExpression& _expr1, llvm::Value*& _value) {
        llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_builder);       
        llvm::Type* type = _expr1.GetType()->mLLVMType;
        llvm::Value* val = llvm::ConstantInt::get(type, 0x1);
        _value = builder->CreateAnd(_expr1.GetValue(), val, "");
        _value = builder->CreateXor(_value, val, "");
        return(eWNOK);
    }

    eWNTypeError GenerateIntNegation(llvm::IRBuilderBase* _builder, const WNExpression&  _expr1, llvm::Value*& _value) {
        llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_builder);       
        llvm::Type* type = _expr1.GetType()->mLLVMType;
        llvm::Value* val = llvm::ConstantInt::get(type, static_cast<WN_UINT64>(-1));
        _value = builder->CreateMul(_expr1.GetValue(), val, "", false, false);
        return(eWNOK);
    }

    struct GenerateIntConstant: public GenerateConstantOperation {
        GenerateIntConstant(WNScriptType _destType) :
            mDestInt(_destType) {
        }
        virtual ~GenerateIntConstant() {}
        virtual eWNTypeError Execute(llvm::IRBuilderBase*, const WN_CHAR* _constant, llvm::Value*& _outLocation) const {
            WN_BOOL consumedString = false;
            WN_INT64 constVal = WNStrings::WNStrToLL(_constant, consumedString);
            if(!consumedString) {
                return(eWNInvalidConstant);
            }
            uint64_t tVal = static_cast<uint64_t>(constVal);
            _outLocation = llvm::ConstantInt::get(mDestInt->mLLVMType, tVal);
            return(eWNOK);
        }
    private:
        WNScriptType mDestInt;
    };

    struct GenerateCharConstant: public GenerateConstantOperation {
        GenerateCharConstant(WNScriptType _destType) :
            mDestInt(_destType) {
        }
        virtual ~GenerateCharConstant() {}
        virtual eWNTypeError Execute(llvm::IRBuilderBase*, const WN_CHAR* _constant, llvm::Value*& _outLocation) const {
            WN_INT32 val = 0;
            const WN_CHAR* ptr = _constant;
            if(*ptr == '\\') {
                ptr++;
                switch(*ptr) {
                case 'a':
                    val = '\a';
                    break;
                case 'b':
                     val = '\b';
                     break;
                case 'f':
                    val = '\f';
                    break;
                case 'n':
                    val = '\n';
                    break;
                case 'r':
                    val = '\r';
                    break;
                case 't':
                    val = '\t';
                    break;
                case '\\':
                    val = '\\';
                    break;
                case '\'':
                    val = '\'';
                    break;
                case '\"':
                    val = '\"';
                    break;
                default:
                    return(eWNInvalidConstant);
                }
            } else {
                val = *ptr;
            }
            _outLocation = llvm::ConstantInt::get(mDestInt->mLLVMType, val);
            return(eWNOK);
        }
    private:
        WNScriptType mDestInt;
    };

    struct GenerateBoolConstant: public GenerateConstantOperation {
        GenerateBoolConstant(WNScriptType _destType) :
            mDestInt(_destType) {
        }
        virtual ~GenerateBoolConstant() {}
        virtual eWNTypeError Execute(llvm::IRBuilderBase*, const WN_CHAR* _constant, llvm::Value*& _outLocation) const {
            WN_BOOL bConst = WNStrings::WNStrNCmp(_constant, "true", 4) == 0;
            _outLocation = llvm::ConstantInt::get(mDestInt->mLLVMType, bConst? 1 : 0);
            return(eWNOK);
        }
    private:
        WNScriptType mDestInt;
    };

    struct GenerateVoidConstant: public GenerateConstantOperation {
        GenerateVoidConstant(WNScriptType _destType) :
            mDestInt(_destType) {
        }
        virtual ~GenerateVoidConstant() {}
        virtual eWNTypeError Execute(llvm::IRBuilderBase*, const WN_CHAR*, llvm::Value*& _outLocation) const {
            _outLocation = llvm::ConstantInt::get(mDestInt->mLLVMType, 0);
            return(eWNOK);
        }
    private:
        WNScriptType mDestInt;
    };

    eWNTypeError WNBuiltInInitializer::InitializeIntTypes(WNTypeManager& _manager) {
        WNScriptType intType;
        WNScriptType charType;
        WNScriptType boolType;
        WNScriptType voidType;
        WNScriptType sizeTType;

        WN_RELEASE_ASSERT(_manager.RegisterScalarType("Int", 50.0f, intType, llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()) ) == eWNOK);
        WN_RELEASE_ASSERT(_manager.RegisterScalarType("Char", 25.0f, charType, llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()) ) == eWNOK);
        WN_RELEASE_ASSERT(_manager.RegisterScalarType("Bool", 10.0f, boolType, llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()) ) == eWNOK);
        WN_RELEASE_ASSERT(_manager.RegisterScalarType("Void", 0.0f, voidType, llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()) ) == eWNOK);
#ifdef _WN_64_BIT
        WN_RELEASE_ASSERT(_manager.RegisterScalarType("-SizeT", 0.f, sizeTType, llvm::IntegerType::getInt64Ty(llvm::getGlobalContext()) ) == eWNOK);
#else
        WN_RELEASE_ASSERT(_manager.RegisterScalarType("-SizeT", 0.f, sizeTType, llvm::IntegerType::getInt32Ty(llvm::getGlobalContext()) ) == eWNOK);
#endif
        _manager.RegisterArithmeticOperator(AR_ADD, intType, intType,
                WN_NEW GenerateIntArithmetic<&llvm::IRBuilder<>::CreateAdd >(intType));
        _manager.RegisterArithmeticOperator(AR_SUB, intType, intType,
                WN_NEW GenerateIntArithmetic<&llvm::IRBuilder<>::CreateSub >(intType));
        _manager.RegisterArithmeticOperator(AR_DIV, intType, intType,
                WN_NEW GenerateIntDivArithmetic<&llvm::IRBuilder<>::CreateSDiv> (intType));
        _manager.RegisterArithmeticOperator(AR_MULT, intType, intType, 
                WN_NEW GenerateIntArithmetic<&llvm::IRBuilder<>::CreateMul>(intType));
        _manager.RegisterArithmeticOperator(AR_MOD, intType, intType,
                WN_NEW GenerateIntModArithmetic<&llvm::IRBuilder<>::CreateSRem>(intType));
        _manager.RegisterArithmeticOperator(AR_EQ, intType, intType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpEQ>(boolType));
        _manager.RegisterArithmeticOperator(AR_NEQ, intType, intType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpNE>(boolType));
        _manager.RegisterArithmeticOperator(AR_LEQ, intType, intType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpSLE>(boolType));
        _manager.RegisterArithmeticOperator(AR_GEQ, intType, intType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpSGE>(boolType));
        _manager.RegisterArithmeticOperator(AR_LT, intType, intType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpSLT>(boolType));
        _manager.RegisterArithmeticOperator(AR_GT, intType, intType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpSGT>(boolType));
        _manager.RegisterPreUnaryOperator(UN_PREDEC, intType,
                WN_NEW PreUnaryFunction<&GeneratePreValue<&llvm::IRBuilder<>::CreateSub> >());
        _manager.RegisterPreUnaryOperator(UN_PREINC, intType,
                WN_NEW PreUnaryFunction<&GeneratePreValue<&llvm::IRBuilder<>::CreateAdd> >());
        _manager.RegisterPostUnaryOperator(UN_POSTDEC, intType,
                WN_NEW PostUnaryFunction<&GeneratePostValue<&llvm::IRBuilder<>::CreateSub> >());
        _manager.RegisterPostUnaryOperator(UN_POSTINC, intType,
                WN_NEW PostUnaryFunction<&GeneratePostValue<&llvm::IRBuilder<>::CreateAdd> >());
        _manager.RegisterPreUnaryOperator(UN_NEG, intType,
            WN_NEW PreUnaryFunction<&GenerateIntNegation>());
        _manager.RegisterConstantOperator(intType, 
            WN_NEW GenerateIntConstant(intType));
        _manager.RegisterAllocationOperator(intType,
            WN_NEW GenerateDefaultAllocation());
        _manager.RegisterAssignmentOperator(intType, AT_EQ,
            WN_NEW GenerateDefaultAssignment());
        
        _manager.RegisterArithmeticOperator(AR_ADD, charType, charType,
                WN_NEW GenerateIntArithmetic<&llvm::IRBuilder<>::CreateAdd >(charType));
        _manager.RegisterArithmeticOperator(AR_SUB, charType, charType,
                WN_NEW GenerateIntArithmetic<&llvm::IRBuilder<>::CreateSub >(charType));
        _manager.RegisterArithmeticOperator(AR_DIV, charType, charType,
                WN_NEW GenerateIntDivArithmetic<&llvm::IRBuilder<>::CreateSDiv> (charType));
        _manager.RegisterArithmeticOperator(AR_MULT, charType, charType, 
                WN_NEW GenerateIntArithmetic<&llvm::IRBuilder<>::CreateMul>(charType));
        _manager.RegisterArithmeticOperator(AR_MOD, charType, charType,
                WN_NEW GenerateIntModArithmetic<&llvm::IRBuilder<>::CreateSRem>(charType));
        _manager.RegisterArithmeticOperator(AR_EQ, charType, charType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpEQ>(boolType));
        _manager.RegisterArithmeticOperator(AR_NEQ, charType, charType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpNE>(boolType));
        _manager.RegisterArithmeticOperator(AR_LEQ, charType, charType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpSLE>(boolType));
        _manager.RegisterArithmeticOperator(AR_GEQ, charType, charType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpSGE>(boolType));
        _manager.RegisterArithmeticOperator(AR_LT, charType, charType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpSLT>(boolType));
        _manager.RegisterArithmeticOperator(AR_GT, charType, charType, 
                WN_NEW GenerateIntCompare<&llvm::IRBuilder<>::CreateICmpSGT>(boolType));
        _manager.RegisterPreUnaryOperator(UN_PREDEC, charType,
                WN_NEW PreUnaryFunction<&GeneratePreValue<&llvm::IRBuilder<>::CreateSub> >());
        _manager.RegisterPreUnaryOperator(UN_PREINC, charType,
                WN_NEW PreUnaryFunction<&GeneratePreValue<&llvm::IRBuilder<>::CreateAdd> >());
        _manager.RegisterPostUnaryOperator(UN_POSTDEC, charType,
                WN_NEW PostUnaryFunction<&GeneratePostValue<&llvm::IRBuilder<>::CreateSub> >());
        _manager.RegisterPostUnaryOperator(UN_POSTINC, charType,
                WN_NEW PostUnaryFunction<&GeneratePostValue<&llvm::IRBuilder<>::CreateAdd> >());
        _manager.RegisterPreUnaryOperator(UN_NEG, charType,
            WN_NEW PreUnaryFunction<&GenerateIntNegation>());
        _manager.RegisterConstantOperator(charType, 
            WN_NEW GenerateCharConstant(charType));
        _manager.RegisterAllocationOperator(charType,
            WN_NEW GenerateDefaultAllocation());
        _manager.RegisterAssignmentOperator(charType, AT_EQ,
            WN_NEW GenerateDefaultAssignment());
        
        _manager.RegisterConstantOperator(boolType, 
            WN_NEW GenerateBoolConstant(boolType));
        _manager.RegisterPreUnaryOperator(UN_NEG, boolType,
            WN_NEW PreUnaryFunction<&GenerateBoolNegation>());
        _manager.RegisterAllocationOperator(boolType,
            WN_NEW GenerateDefaultAllocation());
        _manager.RegisterAssignmentOperator(boolType, AT_EQ,
            WN_NEW GenerateDefaultAssignment());
        
        _manager.RegisterConstantOperator(voidType, 
            WN_NEW GenerateVoidConstant(voidType));

        return(eWNOK);
    }
}
